// -------------------------------------------------------------------------------------------------
// Framework
// -------------------------------------------------------------------------------------------------

import scala.collection.{ mutable ⇒ mutable }
import mutable.ArraySeq

class ControlFunctionFactory {
	private var mundo: Mundo = null

	def create = (input: String) ⇒ {
		try {
			val (opcode, params) = CommandParser(input)

			opcode match {
				case "Welcome" ⇒
					mundo = new Mundo
					ControlFunction.welcome()
				case "React" ⇒
					val bot = new ReactDlgImpl(params, mundo)
					ControlFunction.react(bot)
					bot.commit
				case "Goodbye" ⇒
					ControlFunction.goodbye()
				case _ ⇒
					"" // OK
			}
		} catch {
			case e: Throwable =>
				System.out.println("Error :(")
				e.printStackTrace()
		}
	}
}

// -------------------------------------------------------------------------------------------------

trait ReactDlg {
	// inputs
	def inputOrElse(key: String, fallback: String): String
	def inputAsIntOrElse(key: String, fallback: Int): Int
	def inputAsXYOrElse(keyPrefix: String, fallback: XY): XY
	def name: String
	def view: View
	def energy: Int
	def time: Int
	def generation: Int
	def isMaster: Boolean

	// outputs
	def move(delta: XY): ReactDlg
	def say(text: String): ReactDlg
	def status(text: String): ReactDlg
	def spawn(offset: XY, params: (String, Any)*): ReactDlg
	def set(params: (String, Any)*): ReactDlg
	def log(text: String): ReactDlg
}

trait MiniReactDlg extends ReactDlg {
	// inputs
	def offsetToMaster: XY

	// outputs
	def explode(blastRadius: Int): ReactDlg
}

class ReactDlgImpl(inputParams: Map[String, String], val mundo: Mundo) extends MiniReactDlg {
	// input
	def inputOrElse(key: String, fallback: String) = inputParams.getOrElse(key, fallback)
	def inputAsIntOrElse(key: String, fallback: Int) = inputParams.get(key).map(_.toInt).getOrElse(fallback)
	def inputAsXYOrElse(key: String, fallback: XY) = inputParams.get(key).map(s ⇒ XY(s)).getOrElse(fallback)

	val name = inputParams("name")
	val view = new View(inputParams("view"))
	val energy = inputParams("energy").toInt
	val time = inputParams("time").toInt
	val generation = inputParams("generation").toInt
	def isMaster = generation == 0
	def offsetToMaster = inputAsXYOrElse("master", XY.Zero)
	private val cartografo = mundo.getCartografoAjustadoYActualizarPlano(name, isMaster, time, view, offsetToMaster)

	// output

	private var stateParams = Map.empty[String, Any] // holds "Set()" commands
	private var commands = "" // holds all other commands
	private var debugOutput = "" // holds all "Log()" output
	private var commited = false

	/** Appends a new command to the command string; returns 'this' for fluent API. */
	private def append(s: String): ReactDlg = { commands += (if (commands.isEmpty) s else "|" + s); ReactDlgImpl.this }

	/** Renders commands and stateParams into a control function return string. */
	def commit(): String = {
		assert(commited == false)
		commited = true;

		if (cartografo.perdido)
			say("Perdido: " + cartografo.msgApartamientoInesperado)

		var result = commands
		if (!stateParams.isEmpty) {
			if (!result.isEmpty) result += "|"
			result += stateParams.map(e ⇒ e._1 + "=" + e._2).mkString("Set(", ",", ")")
		}
		if (!debugOutput.isEmpty) {
			if (!result.isEmpty) result += "|"
			result += "Log(text=" + debugOutput + ")"
		}
		System.out.println("time=" + time + ", name=" + name + ", result=" + result)
		result
	}

	def log(text: String) = { debugOutput += text + "\n"; ReactDlgImpl.this }
	def move(direction: XY): ReactDlg = {
		cartografo.setDireccionMovProcurado(direction)
		append("Move(direction=" + direction + ")")
	}
	def say(text: String) = append("Say(text=" + text + ")")
	def status(text: String) = append("Status(text=" + text + ")")
	def explode(blastRadius: Int) = append("Explode(size=" + blastRadius + ")")
	def spawn(offset: XY, params: (String, Any)*) =
		append("Spawn(direction=" + offset +
			",name=" + ReactDlgImpl.nextName +
			(if (params.isEmpty) "" else "," + params.map(e ⇒ e._1 + "=" + e._2).mkString(",")) +
			")")
	def set(params: (String, Any)*) = { stateParams ++= params; ReactDlgImpl.this }
	def set(keyPrefix: String, xy: XY) = { stateParams ++= List(keyPrefix + "x" -> xy.x, keyPrefix + "y" -> xy.y); ReactDlgImpl.this }
}
object ReactDlgImpl {
	var sequencer = 0
	def nextName = {
		sequencer += 1
		sequencer.toString
	}
}
// -------------------------------------------------------------------------------------------------

/**
 * Utility methods for parsing strings containing a single command of the format
 * "Command(key=value,key=value,...)"
 */
object CommandParser {
	/** "Command(..)" => ("Command", Map( ("key" -> "value"), ("key" -> "value"), ..}) */
	def apply(command: String): (String, Map[String, String]) = {
		/** "key=value" => ("key","value") */
		def splitParameterIntoKeyValue(param: String): (String, String) = {
			val segments = param.split('=')
			(segments(0), if (segments.length >= 2) segments(1) else "")
		}

		val segments = command.split('(')
		if (segments.length != 2)
			throw new IllegalStateException("invalid command: " + command)
		val opcode = segments(0)
		val params = segments(1).dropRight(1).split(',')
		val keyValuePairs = params.map(splitParameterIntoKeyValue).toMap
		(opcode, keyValuePairs)
	}
}

// -------------------------------------------------------------------------------------------------

/**
 * Utility class for managing 2D cell coordinates.
 * The coordinate (0,0) corresponds to the top-left corner of the arena on screen.
 * The direction (1,-1) points right and up.
 */
case class XY(x: Int, y: Int) {
	override def toString = x + ":" + y

	def isNonZero = x != 0 || y != 0
	def isZero = x == 0 && y == 0
	def isNonNegative = x >= 0 && y >= 0

	def updatedX(newX: Int) = XY(newX, y)
	def updatedY(newY: Int) = XY(x, newY)

	def addedToX(dx: Int) = XY(x + dx, y)
	def addedToY(dy: Int) = XY(x, y + dy)

	def unary_- = XY(-this.x, -this.y)
	def +(pos: XY) = XY(x + pos.x, y + pos.y)
	def -(pos: XY) = XY(x - pos.x, y - pos.y)
	def *(pos: XY) = scala.math.abs(x * pos.x + y * pos.y)
	def *(factor: Double) = XY((x * factor).intValue, (y * factor).intValue)

	def distanceTo(pos: XY): Double = (this - pos).length // Phythagorean
	def length: Double = math.sqrt(x * x + y * y) // Phythagorean

	def stepsTo(pos: XY): Int = (this - pos).stepCount // steps to reach pos: max delta X or Y
	def stepCount: Int = x.abs.max(y.abs) // steps from (0,0) to get here: max X or Y

	def signum = XY(x.signum, y.signum)

	def negated = XY(-x, -y)
	def negatedX = XY(-x, y)
	def negatedY = XY(x, -y)

	/** Returns the direction index with 'Right' being index 0, then clockwise in 45 degree steps. */
	def toDirection45: Direction45.Value = {
		val unit = signum
		unit.x match {
			case -1 ⇒
				unit.y match {
					case -1 ⇒
						if (x < y * 3) Direction45.Left
						else if (y < x * 3) Direction45.Up
						else Direction45.UpLeft
					case 0 ⇒
						Direction45.Left
					case 1 ⇒
						if (-x > y * 3) Direction45.Left
						else if (y > -x * 3) Direction45.Down
						else Direction45.LeftDown
				}
			case 0 ⇒
				unit.y match {
					case 1 ⇒ Direction45.Down
					case 0 ⇒ throw new IllegalArgumentException("cannot compute direction index for (0,0)")
					case -1 ⇒ Direction45.Up
				}
			case 1 ⇒
				unit.y match {
					case -1 ⇒
						if (x > -y * 3) Direction45.Right
						else if (-y > x * 3) Direction45.Up
						else Direction45.RightUp
					case 0 ⇒
						Direction45.Right
					case 1 ⇒
						if (x > y * 3) Direction45.Right
						else if (y > x * 3) Direction45.Down
						else Direction45.DownRight
				}
		}
	}

	def rotatedCounterClockwise45 = XY.fromDirection45((signum.toDirection45.id + 1) % 8)
	def rotatedCounterClockwise90 = XY.fromDirection45((signum.toDirection45.id + 2) % 8)
	def rotatedClockwise45 = XY.fromDirection45((signum.toDirection45.id + 7) % 8)
	def rotatedClockwise90 = XY.fromDirection45((signum.toDirection45.id + 6) % 8)

	def wrap(boardSize: XY) = {
		val fixedX = if (x < 0) boardSize.x + x else if (x >= boardSize.x) x - boardSize.x else x
		val fixedY = if (y < 0) boardSize.y + y else if (y >= boardSize.y) y - boardSize.y else y
		if (fixedX != x || fixedY != y) XY(fixedX, fixedY) else this
	}
}

object XY {
	/** Parse an XY value from XY.toString format, e.g. "2:3". */
	def apply(s: String): XY = { val a = s.split(':'); XY(a(0).toInt, a(1).toInt) }

	val Zero = XY(0, 0)
	val One = XY(1, 1)

	val Right = XY(1, 0)
	val RightUp = XY(1, -1)
	val Up = XY(0, -1)
	val UpLeft = XY(-1, -1)
	val Left = XY(-1, 0)
	val LeftDown = XY(-1, 1)
	val Down = XY(0, 1)
	val DownRight = XY(1, 1)

	def fromDirection45(index: Int): XY = Direction45(index).asInstanceOf[Direction45.Val45XY].d /*index match {
		case Direction45.Right ⇒ Right
		case Direction45.RightUp ⇒ RightUp
		case Direction45.Up ⇒ Up
		case Direction45.UpLeft ⇒ UpLeft
		case Direction45.Left ⇒ Left
		case Direction45.LeftDown ⇒ LeftDown
		case Direction45.Down ⇒ Down
		case Direction45.DownRight ⇒ DownRight
	} */

	def fromDirection90(index: Int): XY = Direction45(index).asInstanceOf[Direction90.Val90XY].d /* index match {
		case Direction90.Right ⇒ Right
		case Direction90.Up ⇒ Up
		case Direction90.Left ⇒ Left
		case Direction90.Down ⇒ Down
	} */

	def apply(array: Array[Int]): XY = XY(array(0), array(1))
}

object Direction45 extends Enumeration {
	type Direction45 = Val45XY
	val Right = Value(0, XY.Right)
	val RightUp = Value(1, XY.RightUp)
	val Up = Value(2, XY.Up)
	val UpLeft = Value(3, XY.UpLeft)
	val Left = Value(4, XY.Left)
	val LeftDown = Value(5, XY.LeftDown)
	val Down = Value(6, XY.Down)
	val DownRight = Value(7, XY.DownRight)

	protected final def Value(i: Int, d: XY): Value = new Val45XY(i, d)
	class Val45XY(id: Int, val d: XY) extends Val(id)
}

object Direction90 extends Enumeration {
	type Direction90 = Val90XY
	val Right = Value(0)
	val Up = Value(1)
	val Left = Value(2)
	val Down = Value(3)
	protected final def Value(i: Int, d: XY): Value = new Val90XY(i, d)
	class Val90XY(id: Int, val d: XY) extends Val(id)
}

// -------------------------------------------------------------------------------------------------

class View(val cells: String) {
	val size = math.sqrt(cells.length).toInt
	val center = XY(size / 2, size / 2)

	def apply(relPos: XY) = cellAtRelPos(relPos)

	def indexFromAbsPos(absPos: XY): Int = indexFromAbsPos(absPos.x, +absPos.y)
	def indexFromAbsPos(x: Int, y: Int): Int = x + y * size

	def absPosFromIndex(index: Int) = XY(index % size, index / size)

	def absPosFromRelPos(relPos: XY) = relPos + center
	def absPosFromRelPos(x: Int, y: Int) = new XY(center.x + x, center.y + y)

	def cellAtAbsPos(absPos: XY) = CellOccupantType.apply(cells.charAt(indexFromAbsPos(absPos)))
	def cellAtAbsPos(x: Int, y: Int) = cells.charAt(indexFromAbsPos(x, y))

	def indexFromRelPos(relPos: XY) = indexFromAbsPos(absPosFromRelPos(relPos))
	def indexFromRelPos(x: Int, y: Int) = indexFromAbsPos(absPosFromRelPos(x, y))

	def relPosFromAbsPos(absPos: XY) = absPos - center
	def relPosFromAbsPos(x: Int, y: Int) = new XY(x - center.x, y - center.y)

	def relPosFromIndex(index: Int) = relPosFromAbsPos(absPosFromIndex(index))

	def cellAtRelPos(relPos: XY) = CellOccupantType.apply(cells.charAt(indexFromRelPos(relPos)))
	def cellAtRelPos(x: Int, y: Int) = CellOccupantType.apply(cells.charAt(indexFromRelPos(x, y)))

	def offsetToNearest(cellOccupant: CellOccupantType.Value) = {
		val matchingXY = cells.view.zipWithIndex.filter(_._1 == cellOccupant.symbol)
		if (matchingXY.isEmpty)
			None
		else {
			val nearest = matchingXY.map(p ⇒ relPosFromIndex(p._2)).minBy(_.length)
			Some(nearest)
		}
	}

	override def toString(): String = {
		"\n" + cells.grouped(size).zipWithIndex.map(t => t._1 + " " + t._2.toString + "\n").reduceLeft(_ + _).toString()
	}
}

object CellOccupantType extends Enumeration {
	/** Ocluded by a wall*/
	val Ocluded = Value('?')
	val Empty = Value('_')
	val Wall = Value('W')
	val OurMaster = Value('M')
	val EnemyMaster = Value('m')
	val OurSlave = Value('S')
	val EnemySlave = Value('s')
	/**Zugar */
	val GoodPlant = Value('P')
	/** Toxifera */
	val BadPlant = Value('p')
	/**Fluppet */
	val GoodBeast = Value('B')
	/**Snorg */
	val BadBeast = Value('b')

	def apply(c: Char) = super.apply(c.toInt)

	type Value = CoVal
	class CoVal(c: Char) extends Val(c.toInt) {
		val symbol = c
	}
	protected final def Value(c: Char) = new CoVal(c)
}

